package ciliumdriver

import (
	"fmt"
	v1 "gluenet/internal/agent/api/meta/v1"
	"gluenet/internal/agent/depend"
	"gluenet/internal/agent/driver/k8sdriver"
	"strings"

	"golang.org/x/sync/errgroup"
)

func init() {
	depend.Provide(NewSimpleGroupManager)
}

type Group struct {
	Namespace string
	Name      string
	Pods      map[string]struct{}
}

func NewGroup(namespace, name string) Group {
	return Group{
		Name:      name,
		Namespace: namespace,
		Pods:      make(map[string]struct{}),
	}
}

func (g Group) String() string {
	keys := make([]string, 0, len(g.Pods))
	for k := range g.Pods {
		keys = append(keys, k)
	}

	return fmt.Sprintf("%s/%s : %s", g.Namespace, g.Name, strings.Join(keys, `|`))

}

type SimpleGroupManager struct {
	kd k8sdriver.K8sDriver
}

func NewSimpleGroupManager(kd k8sdriver.K8sDriver) *SimpleGroupManager {
	return &SimpleGroupManager{kd}
}

func (g *SimpleGroupManager) Create(group *Group, pod ...string) error {

	// create cnp
	if err := g.kd.Apply(
		strings.NewReader(
			toGroupYaml(group.Name),
		),
		&v1.ApplyOptions{
			NameSpace: group.Namespace,
		},
	); err != nil {
		return err
	}

	return g.Add(group, pod)
}

func (g *SimpleGroupManager) Delete(group *Group) error {

	if err := g.Del(group, g.ListPods(group)); err != nil {
		return err
	}
	return g.kd.Delete(
		strings.NewReader(
			toGroupYaml(group.Name),
		),
		&v1.DeleteOptions{
			NameSpace: group.Namespace,
		})

}

func (g *SimpleGroupManager) Add(group *Group, pods []string) error {
	var eg errgroup.Group

	for _, pod := range pods {
		if _, ok := group.Pods[pod]; ok {
			continue
		}

		// lable pod
		pod := pod
		eg.Go(func() error {

			if err := g.kd.LabelPod(
				group.Namespace,
				pod,
				map[string]string{
					group.Name: "in",
				},
			); err != nil {
				return err
			}

			group.Pods[pod] = struct{}{}
			return nil
		})
	}

	return eg.Wait()
}

func (g *SimpleGroupManager) Del(group *Group, pods []string) error {
	var eg errgroup.Group
	for _, pod := range pods {
		// unlabel pod
		if _, ok := group.Pods[pod]; !ok {
			return fmt.Errorf("invalid pod: %s", pod)
		}

		pod := pod
		eg.Go(func() error {

			if err := g.kd.UnLabelPod(
				group.Namespace,
				pod,
				[]string{group.Name},
			); err != nil {
				return err
			}
			delete(group.Pods, pod)

			return nil
		})

	}

	return eg.Wait()
}

func (g *SimpleGroupManager) ListPods(group *Group) []string {
	keys := make([]string, 0, len(group.Pods))
	for k := range group.Pods {
		keys = append(keys, k)
	}

	return keys
}

const (
	keyGroup = `{groupName}`
)

var groupCnp = `
apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: {groupName}
specs:
  - endpointSelector:
      matchLabels:
        {groupName}: in
    ingress:
    - fromEndpoints:
      - matchLabels:
          {groupName}: in
`

func toGroupYaml(name string) string {
	return strings.ReplaceAll(groupCnp, keyGroup, name)
}
